#pragma once
#include "../Encrypter.h"
#include "../RandomGenerator.h"
#include "../Utils.hpp"
#include <chrono>
#include <nlohmann/json.hpp>
#include <string>

namespace Utils {

class Jwt {
  public:
    struct Header {
        using json = nlohmann::json;
        std::string alg;
        std::string typ;

        Header() : alg("HS256"), typ("JWT") {}

        json to_json() const {
            auto j = json{{"alg", this->alg}, {"typ", this->typ}};
            return j;
        }

        static Header from_json(const json &j) {
            Header header;
            j.at("alg").get_to(header.alg);
            j.at("typ").get_to(header.typ);
            return header;
        }
    };

    struct Payload {
        using json = nlohmann::json;
        std::string sub;
        std::string name;
        size_t iat;
        size_t exp;
        std::string role;

        Payload() = default;

        Payload(std::string sub, std::string name, size_t exp = 3000,
                const std::string &role = "customer")
            : sub(sub), name(name), exp(exp), role(role) {
            auto now = std::chrono::system_clock::now();
            auto timeStamp = std::chrono::duration_cast<std::chrono::seconds>(
                                 now.time_since_epoch())
                                 .count();
            iat = timeStamp;
        }

        json to_json() const {
            auto j = json{{"sub", sub},
                          {"name", name},
                          {"iat", iat},
                          {"exp", exp},
                          {"role", role}};
            return j;
        }

        static Payload from_json(const json &j) {
            Payload payload;
            j.at("sub").get_to(payload.sub);
            j.at("name").get_to(payload.name);
            j.at("iat").get_to(payload.iat);
            j.at("exp").get_to(payload.exp);
            j.at("role").get_to(payload.role);
            return payload;
        }
    };

    struct SerializeResult {
        std::string secretKey;
        std::string jwt;
    };

    enum class ValidationResult { Valid, Expired, NotMatched };

    SerializeResult serialize(const Header &header, const Payload &payload);

    ValidationResult validate(const std::string &jwtStr,
                              const std::string &secretKey);

    Jwt(Jwt &&) = delete;

    ~Jwt();

  private:
    Jwt();
    class JwtImpl;

  public:
    static Jwt &instance();

  private:
    std::unique_ptr<JwtImpl> impl;
};

} // namespace Utils
